<?php defined('SYS_PATH') or die('No direct script access.');
/**
 * Database class
 *
 * @author zendzhang<zendzhang@hotmail.com>
 * @version $Id: Database.php 9 2013-01-14 03:24:50Z zendzhang@hotmail.com $
 */
class TrueBlood_Database extends TrueBlood
{
    /**
     * Configuration
     *
     * @var array
     */
    protected $_config = array
    (
        'hostname' => '127.0.0.1',
        'hostport' => 3306,
        'username' => 'root',
        'password' => '',
        'database' => 'test',
        'charset'  => 'utf8',
        'pconnect' => false,
        'debug'    => false
    );

    /**
     * Database connection
     *
     * @var object |resource|null
     */
    protected $_connection = null;

    /**
     * Query handler
     *
     * @var resource
     */
    protected $_query = null;

    /**
     * Last query sql
     *
     * @var string
     */
    protected $_sql;

    /**
     * Start timestamp
     *
     * @var float
     */
    protected $_start;

    /**
     * Database instances
     *
     * @var array
     */
    public static $_instances = array();

    /**
     * Returns a singleton instance of database
     *
     * @param string $name
     * @return object
     */
    public static function &instance($name)
    {
        if (!isset(self::$_instances[$name]))
        {
            $databases = self::$load->config('databases');
            if (!isset($databases[$name]))
            {
                throw new TrueBlood_Exception("databases.{$name} undefined, check configs/databases.inc.php");
            }
            self::$_instances[$name] = new self($databases[$name]);
        }
        return self::$_instances[$name];
    }

    /**
     * Constructor
     *
     * @param array $config
     */
    public function __construct(array $config = null)
    {
        if (null === $config)
        {
            $databases = $this->load->config('databases');
            if (isset($databases['default']))
            {
                $config = $databases['default'];
            }
        }
        $this->_config = (array)$config + $this->_config;

        $this->log('debug', 'TrueBlood_Database Class Initialized');

        $this->connect();
    }

    /**
     * Debug or not
     *
     * @param bool $debug
     * @return void
     */
    public function debug($debug)
    {
        $this->_config['debug'] = $debug;
    }

    /**
     * Connect to MySQL
     *
     * @return resource
     */
    public function connect()
    {
        if (!extension_loaded('mysql'))
        {
            throw new TrueBlood_Exception('Can not find mysql extension.');
        }
        $this->_start = microtime(true);
        $func = $this->_config['pconnect'] ? 'mysql_pconnect' : 'mysql_connect';
        $connection = @$func($this->_config['hostname'] . ':' . $this->_config['hostport'], $this->_config['username'], $this->_config['password']);
        if (is_resource($connection) && mysql_select_db($this->_config['database'], $connection))
        {
            $this->_connection = $connection;
            $this->query("SET NAMES '" . str_replace('-', '', $this->_config['charset']) . "';");
            return $this->_connection;
        }
        $message = "Can't connect to MySQL server: " . $this->error();
        $this->logMessage('error', $message);
        throw new TrueBlood_Exception($message);
    }

    /**
     * Reconnect to MySQL
     *
     * @return resource
     */
    function reconnect()
    {
        if (false === @mysql_ping($this->_connection))
        {
            $this->close();
            return $this->connect($this->_config);
        }
        return $this->_connection;
    }

    /**
     * Select
     *
     * @param array $params
     * @return array
     */
    public function select($params)
    {
        if (is_string($params))
        {
            $params = array('where' => $params);
        }
        $params = $params + array
        (
            'fields' => '*',
            'where'  => 1,
            'order'  => null,
            'start'  => -1,
            'limit'  => -1
        );
        extract($params);
        $sql = "SELECT {$fields} FROM $table WHERE " . $this->_where($where);
        if ($order)
        {
            $sql .= " ORDER BY {$order}";
        }
        if (0 <= $start && 0 <= $limit)
        {
            $sql .= " LIMIT {$start}, {$limit}";
        }
        return $this->result($sql);
    }

    /**
     * Insert
     *
     * @param array $insert
     * @param string $table
     * @param string $operate
     * @return int
     */
    public function insert($insert, $table, $operate = 'INSERT')
    {
        $fields = $values = array();
        foreach ($insert as $k => $v)
        {
            $fields[] = $k;
            $values[] = "'" . $this->escape($v) . "'";
        }
        $sql = "{$operate} INTO {$table}(" . implode(",", $fields) . ") VALUES(" . implode(",", $values) . ")";
        return $this->result($sql);
    }

    /**
     * Update
     *
     * @param array $update
     * @param mixed $where
     * @param string $table
     * @param bool $formula
     * @return int
     */
    public function update($update, $where, $table, $formula = false)
    {
        $sql = "UPDATE {$table} SET ";
        $i = 0;
        foreach ($update as $k => $v)
        {
            if ($i > 0)
            {
                $sql .= ",";
            }
            $sql .= $k . "=" . ($formula ? $v : "'" . $this->escape($v) . "'");
            $i++;
        }
        $sql .= " WHERE " . $this->_where($where);
        return $this->result($sql);
    }

    /**
     * Delete
     *
     * @param mixed $where
     * @param string $table
     * @return int
     */
    public function delete($where, $table)
    {
        $sql = "DELETE FROM $table WHERE " . $this->_where($where);
        return $this->result($sql);
    }

    /**
     * Count
     *
     * @param mixed $where
     * @param string $table
     * @return int
     */
    public function count($where, $table)
    {
        $sql = "SELECT COUNT(1) AS cnt FROM $table WHERE " . $this->_where($where);
        $this->query($sql);
        $result = $this->fetch();
        return empty($result['cnt']) ? 0 : $result['cnt'];
    }

    /**
     * Get a result row
     *
     * @param string $sql
     * @param string $type
     * @return array
     */
    public function row($sql, $type = 'ASSOC')
    {
        $this->query($sql);
        return $this->fetch($type);
    }

    /**
     * Query SQL
     *
     * @param string $sql
     * @return void
     */
    public function query($sql)
    {
        $this->_start = microtime(true);
        $this->_sql = $sql;
        if (false === ($this->_query = mysql_query($sql, $this->_connection)))
        {
            $this->reconnect();
            if (false === ($this->_query = mysql_query($sql, $this->_connection)))
            {
                $message = "Invalid query: " . $this->error();
                $this->logMessage('error', $message);
                throw new TrueBlood_Exception($message);
            }
        }
        $this->logMessage('info');
        return true;
    }

    /**
     * Fetch one row result
     *
     * @param string $type
     * @return mixd
     */
    public function fetch($type = 'ASSOC')
    {
        $type = strtoupper($type);
        switch ($type)
        {
            case 'ASSOC' :
                $func = 'mysql_fetch_assoc';
                break;
            case 'NUM' :
                $func = 'mysql_fetch_array';
                break;
            case 'OBJECT' :
                $func = 'mysql_fetch_object';
                break;
            default :
                $func = 'mysql_fetch_assoc';
        }
        return $func($this->_query);
    }

    /**
     * Fetch All result
     *
     * @param string $type
     * @return array
     */
    public function fetchAll($type = 'ASSOC')
    {
        switch ($type)
        {
            case 'ASSOC' :
                $func = 'mysql_fetch_assoc';
                break;
            case 'NUM' :
                $func = 'mysql_fetch_array';
                break;
            case 'OBJECT' :
                $func = 'mysql_fetch_object';
                break;
            default :
                $func = 'mysql_fetch_assoc';
        }
        $result = array();
        while (false != ($row = $func($this->_query)))
        {
            $result[] = $row;
        }
        @mysql_free_result($this->_query);
        return $result;
    }

    /**
     * Get SQL result
     *
     * @param string $sql
     * @param string $type
     * @return mixed
     */
    public function result($sql, $type = 'ASSOC')
    {
        return $this->sql($sql, $type);
    }

    /**
     * Get SQL result
     *
     * @param string $sql
     * @param string $type
     * @return mixed
     */
    public function sql($sql, $type = 'ASSOC')
    {
        $this->query($sql);
        $tags = explode(' ', $sql, 2);
        switch (strtoupper($tags[0]))
        {
            case 'SELECT' :
                ($result = $this->fetchAll($type)) || ($result = array());
                break;
            case 'INSERT' :
            case 'REPLACE' :
                $result = $this->lastInsertId();
                break;
            case 'UPDATE' :
            case 'DELETE' :
                $result = $this->affectedRows();
                break;
            default :
                $result = $this->_query;
        }
        return $result;
    }

    /**
     * Generates the WHERE portion of the query
     *
     * @param mixed $where
     * @return string
     */
    public function _where($where)
    {
        if (is_string($where))
        {
            return trim($where);
        }
        if (!is_array($where) || empty($where))
        {
            return '';
        }
        $itmes = array();
        foreach ($where as $k => $v)
        {
            $itmes[] = $k . "='" . $this->escape(trim($v)) . "'";
        }
        return implode(" AND ", $itmes);
    }

    /**
     * Escape string
     *
     * @param string $str
     * @return string
     */
    public function escape($str)
    {
        return mysql_escape_string($str);
    }

    /**
     * Get last insert id
     *
     * @return int
     */
    public function lastInsertId()
    {
        return mysql_insert_id($this->_connection);
    }

    /**
     * Return the rows affected of the last sql
     *
     * @return int
     */
    public function affectedRows()
    {
        return mysql_affected_rows($this->_connection);
    }

    /**
     * Free result
     */
    public function free()
    {
        mysql_free_result($this->_query);
    }

    /**
     * Close mysql connection
     */
    public function close()
    {
        if (is_resource($this->_connection))
        {
            mysql_close($this->_connection);
        }
    }

    /**
     * Get error
     *
     * @return string |array
     */
    public function error($type = 'STRING')
    {
        $type = strtoupper($type);
        if (is_resource($this->_connection))
        {
            $errno = mysql_errno($this->_connection);
            $error = mysql_error($this->_connection);
        }
        else
        {
            $errno = mysql_errno();
            $error = mysql_error();
        }
        if ('ARRAY' == $type)
        {
            return array
            (
                'code' => $errno,
                'msg' => $error
            );
        }
        return $errno . ':' . $error;
    }

    protected function logMessage($level, $message = '')
    {
        $route = $this->router->getMatchedRoute();
        $log = array
        (
            'url'    => $route['controller'] . '/' . $route['action'],
            'time'   => round(microtime(true) - $this->_start, 3),
            'server' => $this->_config['hostname'] . ":" . $this->_config['hostport'] . "/" . $this->_config['database'],
            'sql'    => $this->_sql
        );
        if (!empty($message))
        {
            $log['message'] = $message;
        }
        parent::log($level, '[DB]' . "\t" . implode("\t", $log));

        if ($this->_config['debug'])
        {
            echo "<!--\n";
            echo "---------------------------\n";
            foreach ($log as $k => $v)
            {
                echo $k . " : " . $v . "\n";
            }
            echo "-->\n";
        }
    }
}

?>
